package system

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/gogf/gf/v2/util/grand"
	"github.com/gogf/gf/v2/util/guid"
	"github.com/mssola/user_agent"
	"github.com/sagoo-cloud/sagooiot/internal/consts"
	"github.com/sagoo-cloud/sagooiot/internal/dao"
	"github.com/sagoo-cloud/sagooiot/internal/logic/common"
	"github.com/sagoo-cloud/sagooiot/internal/model"
	"github.com/sagoo-cloud/sagooiot/internal/model/entity"
	"github.com/sagoo-cloud/sagooiot/internal/service"
	"github.com/sagoo-cloud/sagooiot/utility/utils"
)

type sLogin struct {
}

func loginNew() *sLogin {
	return &sLogin{}
}

func init() {
	service.RegisterLogin(loginNew())
}

// Login 登录
func (s *sLogin) Login(ctx context.Context, verifyKey string, captcha string, userName string, password string) (loginUserOut *model.LoginUserOut, token string, err error) {
	//判断验证码是否正确
	if !service.Captcha().VerifyString(verifyKey, captcha) {
		err = gerror.New("验证码输入错误")
		return
	}
	//获取IP地址
	ip := utils.GetClientIp(ctx)
	//获取user-agent
	userAgent := utils.GetUserAgent(ctx)
	//根据账号密码获取用户信息
	userInfo, err := service.SysUser().GetAdminUserByUsernamePassword(ctx, userName, password)
	if err != nil {
		// 保存登录失败的日志信息
		service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
			Status:    0,
			Username:  userName,
			Ip:        ip,
			UserAgent: userAgent,
			Msg:       err.Error(),
			Module:    "系统后台",
		})
		return
	}
	//生成token
	key := gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + gmd5.MustEncryptString(userInfo.UserPassword)
	if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() {
		key = gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + "-" + gmd5.MustEncryptString(userInfo.UserPassword+ip+userAgent)
	}
	userInfo.UserPassword = ""
	token, err = service.SysToken().GenerateToken(ctx, key, userInfo)
	if err != nil {
		return
	}
	//转换用户信息
	if err = gconv.Scan(userInfo, &loginUserOut); err != nil {
		return
	}
	//修改用户信息
	err = service.SysUser().UpdateLoginInfo(ctx, userInfo.Id, ip)
	if err != nil {
		return
	}
	// 保存登录成功的日志信息
	service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
		Status:    1,
		Username:  userName,
		Ip:        ip,
		UserAgent: userAgent,
		Msg:       "登录成功",
		Module:    "系统后台",
	})

	//查看是否在线用户信息是否都存在不存在则删除
	userOnlines, _ := service.SysUserOnline().GetAll(ctx)
	if len(userOnlines) > 0 {
		var onlineIds []uint
		for _, online := range userOnlines {
			if !common.Cache().Get(ctx, online.Key).Bool() {
				onlineIds = append(onlineIds, online.Id)
			}
		}
		if len(onlineIds) > 0 {
			err = service.SysUserOnline().DelByIds(ctx, onlineIds)
		}
	}

	//保存在线用户信息
	ua := user_agent.New(userAgent)
	os := ua.OS()
	explorer, _ := ua.Browser()
	service.SysUserOnline().Invoke(ctx, &entity.SysUserOnline{
		Uuid:     guid.S(),
		UserName: userName,
		Key:      key,
		Token:    token,
		Ip:       ip,
		Os:       os,
		Explorer: explorer,
	})
	return
}

func (s *sLogin) LoginOut(ctx context.Context) (err error) {
	authorization := ghttp.RequestFromCtx(ctx).Header.Get("Authorization")
	if authorization == "" {
		err = gerror.New("请先登录!")
		return
	}
	//查看是否已经登录
	token := strings.Split(authorization, " ")
	if len(token) < 2 || len(token) >= 2 && token[1] == "" {
		err = gerror.New("TOKEN错误!")
		return
	}
	userOnline, _ := service.SysUserOnline().GetInfoByToken(ctx, token[1])
	if userOnline == nil {
		err = gerror.New("未登录,无法退出!")
		return
	}
	cache := common.Cache()
	//增加删除缓存信息
	cache.Remove(ctx, userOnline.Key)

	loginUserId := service.Context().GetUserId(ctx)

	cache.Remove(ctx, consts.CacheUserAuthorize+"_"+gconv.String(loginUserId))
	cache.Remove(ctx, consts.CacheUserInfo+"_"+gconv.String(loginUserId))

	err = service.SysUserOnline().DelByToken(ctx, token[1])
	return
}

func (s *sLogin) BindLogin(ctx context.Context, in model.BindSysUserIn) (loginUserOut *model.LoginUserOut, token string, err error) {
	//获取user-agent
	userAgent := utils.GetUserAgent(ctx)
	//获取IP地址
	ip := utils.GetClientIp(ctx)
	//根据账号密码获取用户信息
	userInfo, err := service.SysUser().GetAdminUserByUsernamePassword(ctx, in.UserName, in.Password)
	if err != nil {
		// 保存登录失败的日志信息
		service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
			Status:    0,
			Username:  in.UserName,
			Ip:        ip,
			UserAgent: userAgent,
			Msg:       err.Error(),
			Module:    "系统后台",
		})
		return
	}
	//生成token
	key := gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + gmd5.MustEncryptString(userInfo.UserPassword)
	if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() {
		key = gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + "-" + gmd5.MustEncryptString(userInfo.UserPassword+ip+userAgent)
	}
	userInfo.UserPassword = ""
	token, err = service.SysToken().GenerateToken(ctx, key, userInfo)
	if err != nil {
		return
	}
	//转换用户信息
	if err = gconv.Scan(userInfo, &loginUserOut); err != nil {
		return
	}
	//修改用户信息
	err = service.SysUser().UpdateLoginInfo(ctx, userInfo.Id, ip)
	if err != nil {
		return
	}
	// 保存登录成功的日志信息
	service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
		Status:    1,
		Username:  in.UserName,
		Ip:        ip,
		UserAgent: userAgent,
		Msg:       "登录成功",
		Module:    "系统后台",
	})

	//查看是否在线用户信息是否都存在不存在则删除
	userOnlines, _ := service.SysUserOnline().GetAll(ctx)
	if len(userOnlines) > 0 {
		var onlineIds []uint
		for _, online := range userOnlines {
			if !common.Cache().Get(ctx, online.Key).Bool() {
				onlineIds = append(onlineIds, online.Id)
			}
		}
		if len(onlineIds) > 0 {
			err = service.SysUserOnline().DelByIds(ctx, onlineIds)
		}
	}

	//保存在线用户信息
	ua := user_agent.New(userAgent)
	os := ua.OS()
	explorer, _ := ua.Browser()
	service.SysUserOnline().Invoke(ctx, &entity.SysUserOnline{
		Uuid:     guid.S(),
		UserName: in.UserName,
		Key:      key,
		Token:    token,
		Ip:       ip,
		Os:       os,
		Explorer: explorer,
	})
	_, err = dao.SysOauthUser.Ctx(ctx).Where(dao.SysOauthUser.Columns().Id, in.OauthUserId).
		Data(g.Map{"user_id": userInfo.Id}).Update()
	return
}

// Login 登录
func (s *sLogin) Oauth(ctx context.Context, in *model.OauthIn) (loginUserOut *model.LoginUserOut, token string, isCreate string, uid int, err error) {

	switch in.Types {
	case "gitee":
		{
			res, err := s.GiteeAccessToken(ctx, in)
			if err != nil {
				return nil, "", "", 0, err
			}
			user, err := s.GiteeTokenUser(ctx, res.AccessToken, in)
			if err != nil {
				return nil, "", "", 0, err
			}
			total, err := service.SystemSysOauthUser().GetSysOauthUserTotal(ctx, user.Id, "gitee")
			if err != nil {
				return nil, "", "", 0, err
			}
			// 已经绑定过账号，直接登录了，不判断是否创建账号
			if total > 0 {
				out, err := service.SystemSysOauthUser().GetSysOauthUserByOpenid(ctx, gconv.String(user.Id))
				if err != nil {
					return nil, "", "", 0, err
				}
				loginUserOut, token, err = s.loginOauthCommon(ctx, out.UserId)
				if err != nil {
					return nil, "", "", 0, err
				}
				return loginUserOut, token, "0", 0, nil
			} else {
				var ou model.SysOauthUserAddInput
				ou.AvatarUrl = user.AvatarURL
				ou.Name = user.Name
				ou.Openid = gconv.String(user.Id)
				ou.Types = in.Types

				//  in.IsCreate == 0  创建新账号
				if in.IsCreate == 0 {
					err = s.oauthCreateUser(ctx, &ou, in.Types)

					if err != nil {
						return nil, "", "", 0, err
					}

					out, err := service.SystemSysOauthUser().GetSysOauthUserByOpenid(ctx, gconv.String(user.Id))
					if err != nil {
						return nil, "", "", 0, err
					}

					loginUserOut, token, err = s.loginOauthCommon(ctx, out.UserId)
					if err != nil {
						return nil, "", "", 0, err
					}
					return loginUserOut, token, "0", 0, nil
				} else {
					// 不创建用户,返回sys_oauth_user的id，后面可以选择绑定账号
					total, _ := service.SystemSysOauthUser().GetSysOauthUserCount(ctx, ou.Openid)
					if total > 0 {
						out, _ := service.SystemSysOauthUser().GetSysOauthUserByOpenid(ctx, ou.Openid)
						return nil, "", "1", out.Id, nil
					} else {
						var oautUser model.SysOauthUserAddInput
						oautUser.AvatarUrl = user.AvatarURL
						oautUser.Name = user.Name
						oautUser.Openid = gconv.String(user.Id)
						err = service.SystemSysOauthUser().AddSysOauthUser(ctx, oautUser)
						if err != nil {
							return nil, "", "", 0, err
						}
						out, _ := service.SystemSysOauthUser().GetSysOauthUserByOpenid(ctx, ou.Openid)
						return nil, "", "1", out.Id, nil
					}

				}

			}
		}
	case "qq":
		{

		}
	case "wechat":
		{

		}
	case "github":
		{
		}
	case "dingding":
		{
		}
	default:
		{
			err = gerror.New("类型错误")
			return
		}
	}
	return
}
func (s *sLogin) giteeLogin(ctx context.Context) {

}
func (s *sLogin) GiteeAccessToken(ctx context.Context, in *model.OauthIn) (token model.GiteeAccessToken, err error) {
	config, err := service.SystemSysOauth().GetSysOauthByName(ctx, in.Types)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	httpClient := g.Client()
	if strings.HasPrefix(config.CodeUrl, "https") {
		httpClient.Transport = tr
	}
	params := map[string]any{
		"code":          in.Code,
		"client_id":     config.ClientId,
		"client_secret": config.ClientSecret,
		"grant_type":    "authorization_code",
		"redirect_uri":  config.RedirectUrl,
	}

	resp, err := httpClient.Post(ctx, config.OauthUrl, params)
	if err != nil {
		return
	}

	defer resp.Close()
	bs := resp.ReadAll()
	fmt.Println(string(bs))
	err = json.Unmarshal(bs, &token)
	if err != nil {
		return
	}
	if token.ErrorDescription != "" && token.Error != "" {
		err = errors.New("code已过期")
		return
	}
	return
}

func (s *sLogin) GiteeTokenUser(ctx context.Context, token string, in *model.OauthIn) (user model.GiteeUser, err error) {
	config, _ := service.SystemSysOauth().GetSysOauthByName(ctx, in.Types)
	url := config.UserUrl + "?access_token=" + token
	httpClient := g.Client()
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	if strings.HasPrefix(url, "https") {
		httpClient.Transport = tr
	}

	resp, err := httpClient.Get(ctx, url)
	if err != nil {
		return
	}

	defer resp.Close()
	bs := resp.ReadAll()

	err = json.Unmarshal(bs, &user)
	return
}
func (s *sLogin) oauthCreateUser(ctx context.Context, user *model.SysOauthUserAddInput, types string) (err error) {

	DepID, RoleID, PostID, err := s.oauthCheckUser(ctx, user, types)
	if err != nil {
		return
	}
	s.oauthInsertUser(ctx, DepID, RoleID, PostID, user, types)
	return
}
func (s *sLogin) oauthInsertUser(ctx context.Context, DepID, RoleID, PostID int, user *model.SysOauthUserAddInput, types string) (err error) {
	config, _ := service.SystemSysOauth().GetSysOauthByName(ctx, types)
	var su entity.SysUser
	su.Avatar = user.AvatarUrl
	su.DeptId = uint64(DepID)
	su.Status = 1
	su.IsAdmin = 1
	su.UserSalt = grand.S(10)
	su.UserPassword = utils.EncryptPassword(config.DefPass, su.UserSalt)
	su.UserTypes = "system"
	su.DeptId = uint64(DepID)
	su.IsDeleted = 0
	su.UserNickname = user.Name
	su.UserName = types + "_" + gconv.String(time.Now().Unix())
	err = dao.SysUser.Transaction(ctx, func(ctx context.Context, _ *gdb.TX) error {
		lastInsertId, err := dao.SysUser.Ctx(ctx).Data(su).InsertAndGetId()
		if err != nil {
			return gerror.New("添加用户失败")
		}
		err = BindUserAndPost(ctx, int(lastInsertId), []int{PostID})
		if err != nil {
			return gerror.New("绑定部门失败")
		}
		err = BindUserAndRole(ctx, int(lastInsertId), []int{RoleID})
		if err != nil {
			return gerror.New("绑定角色失败")
		}
		user.UserId = int(lastInsertId)
		err = service.SystemSysOauthUser().AddSysOauthUser(ctx, *user)
		return err
	})
	return
}
func (s *sLogin) oauthCheckUser(ctx context.Context, user *model.SysOauthUserAddInput, types string) (DepID, RoleID, PostID int, err error) {

	config, _ := service.SystemSysOauth().GetSysOauthByName(ctx, types)
	var role entity.SysRole
	dao.SysRole.Ctx(ctx).Where(dao.SysRole.Columns().Id, config.DefRole).Scan(&role)
	if role.Id == 0 {
		err = gerror.New("不存在角色")
		return
	}
	menus, err := service.SysAuthorize().GetInfoByRoleIdsAndItemsType(ctx, []int{int(role.Id)}, "menu")
	if err != nil {
		err = gerror.New(err.Error())
		return
	}
	if len(menus) == 0 {
		gerror.New("该角色未分配菜单权限")
		return
	}
	var dept entity.SysDept
	dao.SysDept.Ctx(ctx).Where(dao.SysDept.Columns().DeptId, config.DefDept).Scan(&dept)
	if dept.DeptId == 0 {
		err = gerror.New("部门不存在")
		return
	}
	var post entity.SysPost
	dao.SysPost.Ctx(ctx).Where(dao.SysPost.Columns().PostId, config.DefPost).Scan(&post)
	if dept.DeptId == 0 {
		err = gerror.New("岗位不存在")
		return
	}

	return int(dept.DeptId), int(role.Id), int(post.PostId), nil
}
func (s *sLogin) loginOauthCommon(ctx context.Context, userId int) (loginUserOut *model.LoginUserOut, token string, err error) {

	ip := utils.GetClientIp(ctx)
	//获取user-agent
	userAgent := utils.GetUserAgent(ctx)
	//根据账号密码获取用户信息
	var userInfo entity.SysUser
	err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().Id, userId).Scan(&userInfo)
	if err != nil {
		// 保存登录失败的日志信息
		service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
			Status:    0,
			Username:  userInfo.UserName,
			Ip:        ip,
			UserAgent: userAgent,
			Msg:       err.Error(),
			Module:    "系统后台",
		})
		return
	}
	//生成token
	key := gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + gmd5.MustEncryptString(userInfo.UserPassword)
	if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() {
		key = gconv.String(userInfo.Id) + "-" + gmd5.MustEncryptString(userInfo.UserName) + "-" + gmd5.MustEncryptString(userInfo.UserPassword+ip+userAgent)
	}
	userInfo.UserPassword = ""
	token, err = service.SysToken().GenerateToken(ctx, key, userInfo)
	if err != nil {
		return
	}
	//转换用户信息
	if err = gconv.Scan(userInfo, &loginUserOut); err != nil {
		return
	}
	//修改用户信息
	err = service.SysUser().UpdateLoginInfo(ctx, userInfo.Id, ip)
	if err != nil {
		return
	}
	// 保存登录成功的日志信息
	service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{
		Status:    1,
		Username:  userInfo.UserName,
		Ip:        ip,
		UserAgent: userAgent,
		Msg:       "登录成功",
		Module:    "系统后台",
	})

	//查看是否在线用户信息是否都存在不存在则删除
	userOnlines, _ := service.SysUserOnline().GetAll(ctx)
	if len(userOnlines) > 0 {
		var onlineIds []uint
		for _, online := range userOnlines {
			if !common.Cache().Get(ctx, online.Key).Bool() {
				onlineIds = append(onlineIds, online.Id)
			}
		}
		if len(onlineIds) > 0 {
			err = service.SysUserOnline().DelByIds(ctx, onlineIds)
		}
	}

	//保存在线用户信息
	ua := user_agent.New(userAgent)
	os := ua.OS()
	explorer, _ := ua.Browser()
	service.SysUserOnline().Invoke(ctx, &entity.SysUserOnline{
		Uuid:     guid.S(),
		UserName: userInfo.UserName,
		Key:      key,
		Token:    token,
		Ip:       ip,
		Os:       os,
		Explorer: explorer,
	})
	return
}
